
package com.trydofor.id.doer.shell;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.LoggerFactory;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.body.shell.ShellQueryBody;
import com.trydofor.id.body.shell.ShellReplyBody;
import com.trydofor.id.body.shell.UsageQueryBody;
import com.trydofor.id.body.shell.UsageReplyBody;
import com.trydofor.id.byta.Bytable;
import com.trydofor.id.byta.Head;
import com.trydofor.id.byta.Packet;
import com.trydofor.id.doer.PacketDoer;
import com.trydofor.id.logs.Eventable;
import com.trydofor.id.logs.Eventer;
import com.trydofor.id.logs.LogLevel;

/**
 * @author : Shi Rongjiu (www.trydofor.com)
 */
public class ShellDoer implements PacketDoer, Eventable {
    
    /**
     * the events
     */
    public enum Event implements Eventer.Event {
        /**
         * added a new command
         */
        COMMAND_ADDEDNEW,
        /**
         * remove a command
         */
        COMMAND_REMOVED,
        /**
         * replaced a old command with a new one
         */
        COMMAND_REPLACED,
        /**
         * can NOT find a command for the worker and type
         */
        COMMAND_NOTFOUND,
        /**
         * exceuted a command
         */
        COMMAND_EXECUTED
    }
    
    private final Eventer           eventer = new Eventer(LoggerFactory.getLogger(ShellDoer.class));
    private final Map<String, Exec> command = new HashMap<String, Exec>();
    
    /**
     * constructor
     */
    public ShellDoer() {
        // set default event log level
        eventer.register(Event.COMMAND_ADDEDNEW, LogLevel.INFO);
        eventer.register(Event.COMMAND_REMOVED, LogLevel.INFO);
        eventer.register(Event.COMMAND_REPLACED, LogLevel.WARN);
        eventer.register(Event.COMMAND_NOTFOUND, LogLevel.WARN);
        eventer.register(Event.COMMAND_EXECUTED, LogLevel.INFO);
        
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean accept(Packet request) {
        Bytable<?> body = request.getBody();
        boolean usage = (body instanceof UsageQueryBody);
        boolean shell = (body instanceof ShellQueryBody);
        return usage || shell;
    }
    
    /**
     * {@inheritDoc}
     */
    public Packet response(Packet request) {
        Bytable<?> req = request.getBody();
        Bytable<?> res = null;
        if (req instanceof UsageQueryBody) { // usage
            UsageQueryBody query = (UsageQueryBody) req;
            UsageReplyBody body = new UsageReplyBody();
            String name = query.getCommand();
            if (name == null) {
                for (String cmd : command.keySet()) {
                    body.add(cmd, command.get(cmd).cmd);
                }
            }
            else {
                Exec c = command.get(name);
                if (c != null) {
                    body.add(name, c.cmd);
                }
                else {
                    eventer.log(Event.COMMAND_NOTFOUND, "command Not found, name={}", name);
                }
            }
            res = body;
        }
        else { // shell or throw
            ShellQueryBody query = (ShellQueryBody) req; // throw
            String name = query.getCommand();
            Exec exec = command.get(name);
            if (exec == null) {
                eventer.log(Event.COMMAND_NOTFOUND, "command Not found, name={}", name);
                res = new ShellReplyBody(name, -1, null, "command Not found, name=" + name);
            }
            else {
                try {
                    Outcome out = exec.exe.execute(exec.cmd, query.getArgument());
                    eventer.log(Event.COMMAND_EXECUTED, "command executed, command={},output={}", name, out);
                    res = new ShellReplyBody(name, out);
                }
                catch (Exception e) {
                    // should not happen
                    eventer.log(Event.COMMAND_EXECUTED, e, "failed to exceute the command:" + name);
                    res = new ShellReplyBody(name, new Outcome(-100, null, Exceptions.printStackTrace(e)));
                }
            }
        }
        
        Head replyHead = new Head(0, request.getHead().getDealCode());
        Packet reply = new Packet(replyHead, res);
        return reply;
    }
    
    /**
     * {@inheritDoc}
     */
    public Eventer getEventer() {
        return eventer;
    }
    
    /**
     * register the command.
     * 
     * @param name the unique name. SHOULD be format as '(path/)+alias'
     * @param command the command
     * @param executor the executor
     * @throws NullPointerException if name/command/executor is null
     */
    public void register(String name, Command command, Executer executor) {
        Exceptions.checkNullPointer(name);
        Exceptions.checkNullPointer(command);
        Exceptions.checkNullPointer(executor);
        Exec neu = new Exec();
        neu.cmd = command;
        neu.exe = executor;
        
        Exec old = this.command.put(name, neu);
        if (old == null) {
            eventer.log(Event.COMMAND_ADDEDNEW, "added a new command, name={}", name);
        }
        else {
            eventer.log(Event.COMMAND_REPLACED, "replaced the command, name={}, old={}", name, old.cmd);
        }
    }
    
    /**
     * unregister the command.
     * 
     * @param name the unique name
     * @throws NullPointerException if key is null
     * @see Map#remove(Object)
     */
    public void unregister(String name) {
        Exceptions.checkNullPointer(name);
        command.remove(name);
        eventer.log(Event.COMMAND_REMOVED, "unregister a command, name={}", name);
    }
    
    private static class Exec {
        
        private Command  cmd;
        private Executer exe;
    }
}
